﻿namespace CalculatorLibrary
{
    using System;
    using System.Globalization;
    using System.Linq;
    using System.Text;

    using CalculatorLibrary.ExpressionLibrary;

    /// <summary>
    ///     Class Calculator
    /// </summary>
    public class Calculator
    {
        #region Constants

        /// <summary>
        ///     The exit constant
        /// </summary>
        private const string ExitConstant = "/exit";

        /// <summary>
        ///     The help constant
        /// </summary>
        private const string HelpConstant = "/help";

        /// <summary>
        ///     The input constant
        /// </summary>
        private const string InputConstant = "/input";

        #endregion

        #region Static Fields

        /// <summary>
        ///     The allowed chars
        /// </summary>
        private static readonly string DecimalDelimiter;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        ///     Initializes static members of the <see cref="Calculator" /> class.
        /// </summary>
        static Calculator()
        {
            NumberFormatInfo nfi = CultureInfo.CurrentCulture.NumberFormat;
            var decimalSeparator = nfi.NumberDecimalSeparator;
            DecimalDelimiter = decimalSeparator;
        }

        #endregion

        #region Public Methods and Operators

        /// <summary>
        ///     Calculates the specified input string.
        /// </summary>
        /// <param name="inputString">
        ///     The input string.
        /// </param>
        /// <returns>
        ///     System.String.
        /// </returns>
        public CalculationResult Calculate(string inputString)
        {
            var constantKeysCheckResult = CheckInputForConstantKeys(inputString);
            if (constantKeysCheckResult.ReturnState != ReturnState.NA)
            {
                return constantKeysCheckResult;
            }

            var validator = new Validator(DecimalDelimiter);
            if (!validator.Validate(inputString))
            {
                return validator.GetError();
            }

            try
            {
                decimal resultOfCalculation = ProcessData(validator.GetUserExpression());
                return new CalculationResult
                           {
                               IsError = false, 
                               Result = resultOfCalculation, 
                               ReturnState = ReturnState.Continue, 
                               StringResult = string.Empty
                           };
            }
            catch (Exception ex)
            {
                return new CalculationResult
                           {
                               IsError = true, 
                               Result = null, 
                               ReturnState = ReturnState.Continue, 
                               StringResult = string.Format("Unexpected exception: {0}", ex.Message)
                           };
            }
        }

        /// <summary>
        ///     Gets the usage help.
        /// </summary>
        /// <returns>System.String.</returns>
        public string GetUsageHelp()
        {
            var sb = new StringBuilder();
            sb.AppendLine("Usage:");
            sb.AppendLine(string.Concat(HelpConstant, " - shows this text block"));
            sb.AppendLine(string.Concat(InputConstant, " - shows allowed input format"));
            sb.AppendLine(string.Concat(ExitConstant, " - terminates calculation"));
            sb.AppendLine("-----------------------------------");
            return sb.ToString();
        }

        #endregion

        #region Methods

        /// <summary>
        /// Checks the input for constant keys.
        /// </summary>
        /// <param name="inputString">
        /// The input string.
        /// </param>
        /// <returns>
        /// <c>true</c> if XXXX, <c>false</c> otherwise
        /// </returns>
        private CalculationResult CheckInputForConstantKeys(
            string inputString)
        {
            var returnResult = new CalculationResult { IsError = false };

            switch (inputString)
            {
                case HelpConstant:
                    {
                        returnResult.ReturnState = ReturnState.Continue;
                        returnResult.StringResult = GetUsageHelp();
                        break;
                    }

                case InputConstant:
                    {
                        returnResult.ReturnState = ReturnState.Continue;
                        returnResult.StringResult = GetInputFormat();
                        break;
                    }

                case ExitConstant:
                    {
                        returnResult.ReturnState = ReturnState.Terminate;
                        returnResult.StringResult = string.Format("Calculation terminated.");
                        break;
                    }

                default:
                    {
                        returnResult.ReturnState = ReturnState.NA;
                        break;
                    }
            }

            return returnResult;
        }

        /// <summary>
        ///     Gets the allowed operators string.
        /// </summary>
        /// <returns>System.String.</returns>
        private string GetAllowedOperatorsString()
        {
            var sb = new StringBuilder();
            var allOperators = TypesCollection.CalculationTypes.Keys.ToList();
            for (var i = 0; i < allOperators.Count; i++)
            {
                if (i != 0)
                {
                    sb.Append(" ");
                }

                sb.Append(string.Format("'{0}'", allOperators[i]));
            }

            return sb.ToString();
        }

        /// <summary>
        /// Gets the calculation provider.
        /// </summary>
        /// <param name="calculationType">Type of the calculation.</param>
        /// <returns>CalculatorLibrary.ExpressionLibrary.ICalculation.</returns>
        private IExpression GetCalculationProvider(Type calculationType)
        {
            return (IExpression)Activator.CreateInstance(calculationType);
        }

        /// <summary>
        ///     Gets the input format.
        /// </summary>
        /// <returns>System.String.</returns>
        private string GetInputFormat()
        {
            var sb = new StringBuilder();
            sb.AppendLine("Each expression should contain three parts:");
            const string NumbersString = "Any number preceded or not with sign character (+ or -)";
            sb.AppendLine(string.Format("\t1) {0}", NumbersString));
            sb.Append("\t2) One operator of the following list: ");
            sb.AppendLine(GetAllowedOperatorsString());
            sb.AppendLine(string.Format("\t3) {0}", NumbersString));
            sb.Append(
                string.Format("* As a delimiter of decimal part of a number should be used '{0}'", DecimalDelimiter));
            return sb.ToString();
        }

        /// <summary>
        /// The process data.
        /// </summary>
        /// <param name="expression">The expression.</param>
        /// <returns>The <see cref="string" />.</returns>
        private decimal ProcessData(UserExpression expression)
        {
            var calculationProvider = GetCalculationProvider(expression.CalculationType);
            return calculationProvider.Calculate(expression.LeftOperand, expression.RightOperand);
        }

        #endregion
    }
}